﻿using System.Linq;
using System.Collections.Generic;
using DARE.Tools;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System;
using System.Runtime.InteropServices;

namespace DARE
{
    public sealed class CSkeleton : AModel
    {
        #region Fields

        struct VertexPositionColorNormal : IVertexType
        {
            #region Fields

            public Vector3 Position;
            public Color Color;
            public Vector3 Normal;

            private static readonly VertexDeclaration _declaration;

        #endregion

            #region Property

            public VertexDeclaration VertexDeclaration
        {
                get { return _declaration; }
            }

            #endregion

            #region CTOR

            static VertexPositionColorNormal()
            {
                _declaration = new VertexDeclaration(
                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                    new VertexElement(Marshal.SizeOf(Vector3.Zero), VertexElementFormat.Color, VertexElementUsage.Color, 0),
                    new VertexElement(Marshal.SizeOf(Vector3.Zero) + Marshal.SizeOf(Color.White), VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
                    );
            }

            public VertexPositionColorNormal(Vector3 _pos, Color _color, Vector3 _normal)
                {
                Position = _pos;
                Color = _color;
                Normal = _normal;
                }

            #endregion

            #region Method
            #endregion
            }

        /// <summary>
        /// Nested class cone use to create a segment that link two bones
        /// </summary>
        class Cone
        {
            #region Fields

            private const float lenghtStep = 10;

        #endregion

            #region Property

            public CAnimNode Node { get; private set; }

            /// <summary>
            /// IndexBuffer 
            /// </summary>
            public List<Int16> Indexs { get; set; }

            /// <summary>
            /// Vertex buffer
            /// </summary>
            public List<VertexPositionColorNormal> Vertexs { get; set; }

            #endregion

        #region CTOR

            /// <summary>
            /// Constructor of the cone class
            /// </summary>
            /// <param name="absoluteStart">Matrix of the absolute transfom of the start bone</param>
            /// <param name="AbsoluteEnd">Matrix of the absolute transfom of the target bone</param>
            /// <param name="indexStart">Name of the start bone</param>
            /// <param name="indexEnd">Name of the target bone</param>
            /// <param name="scale">Slace of the bone</param>
            public Cone(CTransform absoluteStart, CTransform AbsoluteEnd, CAnimNode _node, float scale)
        {
                Node = _node;

                var Vertex = new List<Vector3>();
                var Normals = new List<Vector3>();
                Vertexs = new List<VertexPositionColorNormal>();
                Indexs = new List<short>();

                Vector3 v1 = AbsoluteEnd.Position - absoluteStart.Position;
                Vector3 v2 = Vector3.UnitY;

                Quaternion rot = Quaternion.Identity;

                float d = Vector3.Dot(v1, v2);

                Vector3 axis = Vector3.Cross(v1, v2);

                float qw = (float)Math.Sqrt(v1.LengthSquared() * v2.LengthSquared()) + d;

                if (qw < 0.0001)
                    rot = new Quaternion(0, -v1.Z, v1.Y, v1.X);
                else
                    rot = new Quaternion(-qw, axis.Z, axis.Y, axis.X);

                rot.Normalize();

                float lenght = v1.Length();
                float sc = scale;

                if (lenght < lenghtStep)
                    sc = (lenght / lenghtStep) * sc;

                Vector3 a = Vector3.Transform(new Vector3(sc, 0, 0), Matrix.CreateFromQuaternion(rot));
                Vector3 b = Vector3.Transform(Vector3.Transform(new Vector3(sc, 0, 0), Matrix.CreateRotationY(MathHelper.ToRadians(-120))), Matrix.CreateFromQuaternion(rot));
                Vector3 c = Vector3.Transform(Vector3.Transform(new Vector3(sc, 0, 0), Matrix.CreateRotationY(MathHelper.ToRadians(120))), Matrix.CreateFromQuaternion(rot));

                Vertex.Add(Vector3.Transform(a, Matrix.CreateTranslation(absoluteStart.Position)));
                Vertex.Add(Vector3.Transform(b, Matrix.CreateTranslation(absoluteStart.Position)));
                Vertex.Add(Vector3.Transform(c, Matrix.CreateTranslation(absoluteStart.Position)));
                Vertex.Add(AbsoluteEnd.Position);

                Normals.AddRange(new Vector3[4] { Vector3.Zero, Vector3.Zero, Vector3.Zero, Vector3.Zero });

                Indexs.AddRange(new Int16[] { 0, 1, 2,
                                                      1, 3, 2,
                                                      2, 3, 0,
                                                      0, 3, 1 });
                for (int i = 0; i < 4; i += 3)
                {
                    v1 = Vertex[Indexs[i + 1]] - Vertex[Indexs[i]];
                    v2 = Vertex[Indexs[i + 2]] - Vertex[Indexs[i]];
                    Vector3 normal = Vector3.Cross(v1, v2);
                    Normals[Indexs[i]] += normal;
                    Normals[Indexs[i + 1]] += normal;
                    Normals[Indexs[i + 2]] += normal;
        }

                foreach (var elem in Normals)
                    elem.Normalize();

                Vertexs.Add(new VertexPositionColorNormal(Vertex[0], Color.LightGray, Normals[0]));
                Vertexs.Add(new VertexPositionColorNormal(Vertex[1], Color.LightGray, Normals[1]));
                Vertexs.Add(new VertexPositionColorNormal(Vertex[2], Color.LightGray, Normals[2]));
                Vertexs.Add(new VertexPositionColorNormal(Vertex[3], Color.LightGray, Normals[3]));
            }

            #endregion
        }

        private List<Cone> m_cones;

        private CAnimationController m_controller;

        private CHierarchy m_hierarchy;

        private BasicEffect m_basicEffect;

        #endregion

        #region CTOR

        public CSkeleton(CHierarchy hierarchy)
            :this(hierarchy, 1.0f)
        {
            
        }

        public CSkeleton(CHierarchy hierarchy, float _scale)
        {
            m_hierarchy = hierarchy;
            m_controller = new CAnimationController(hierarchy);
            Initialize(_scale);
        }
        //}

        public CSkeleton(CModel model)
            : this(model, 1.0f)
        {
        }

        public CSkeleton(CModel model, float _scale)
        {
            m_hierarchy = model.AnimationController.m_hierarchy;
            m_controller = model.AnimationController;
            Initialize(_scale);
        }

        #endregion

        #region Method
        protected override BoundingBox GetMinBoundingBox()
        {
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            foreach (var cone in m_cones) 
                foreach (VertexPositionColorNormal vertex in cone.Vertexs)
                {
                    if (vertex.Position.X < min.X)
                        min.X = vertex.Position.X;
                    if (vertex.Position.Y < min.Y)
                        min.Y = vertex.Position.Y;
                    if (vertex.Position.Z < min.Z)
                        min.Z = vertex.Position.Z;
                    if (vertex.Position.X > max.X)
                        max.X = vertex.Position.X;
                    if (vertex.Position.Y > max.Y)
                        max.Y = vertex.Position.Y;
                    if (vertex.Position.Z > max.Z)
                        max.Z = vertex.Position.Z;
                }
            return new BoundingBox(min, max);
        }

        private void Initialize(float _scale)
        {
            m_basicEffect = new BasicEffect(CDare.Instance.GraphicsDevice);
            m_cones = new List<Cone>();            
            GenerateConeModel(m_hierarchy[0], _scale);
        }

        private void GenerateConeModel(CAnimNode root, float _scale)
        {
            List<CAnimNode> _child = (from el in m_hierarchy.Nodes where el.ParentIndex == root.Index select el).ToList(); // el.IsBones && 

            foreach (var elem in _child)
            {
                m_cones.Add(new Cone(root.AbsolutePose, elem.AbsolutePose, root, _scale));
                GenerateConeModel(elem, _scale);
            }
        }

        public override void Update(GameTime gameTime)
        {
            //m_controller.Update(gameTime, base.Node.WorldMatrix);
        }

    //    public override void Update(GameTime gameTime)
    //    {
    //        //throw new NotImplementedException();
    //}

        public override void Draw(GameTime gameTime)
        {
            if (!Show)
                return;
            //base.Node.Render3D.EffectMgr.SetEffectGlobalValues(ref base.m_effect);

            m_basicEffect.View = this.Node.Render3D.CameraMgr.ActiveCamera.View;
            m_basicEffect.Projection = this.Node.Render3D.CameraMgr.ActiveCamera.Projection;

            
            foreach (var cone in m_cones)
            {
                    if (cone.Node.IsBones)
                        m_basicEffect.World = m_controller.SkinningTransform[m_controller.m_skinindexer.IndexOf(cone.Node.Index)];
                    else
                        m_basicEffect.World = m_controller.WorldTransform[cone.Node.Index];

                    foreach (EffectPass pass in m_basicEffect.CurrentTechnique.Passes)
                    {
                           pass.Apply();


                           // CDare.Instance.DebugMgr.DebugConsole.WriteLine(string.Format(cone.Node.Name + " {0} ", cone.Node.ParentIndex));

                           //base.Effect.WorldInverse = Matrix.Transpose(Matrix.Invert(base.Effect.World));
                    
                           CDare.Instance.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList,
                                                                                cone.Vertexs.ToArray(), 0, 4,
                                                                                cone.Indexs.ToArray(), 0, (int)(cone.Indexs.Count / 3.0f));
                    }
            }
        }

        //public CAnimConfig Play(string animName, bool priority = false, EAnimState state = EAnimState.RUN | EAnimState.LOOP)
        //{
        //    return m_controller.Play(animName, priority, state);
        //}

        #endregion

}
}
